import parole, pygame

import random
import main, sim
from util import *

class Brain():
    waitTime = 1000

    def __init__(self, creature, stateMap):
        self.creature = creature
        self.stateMap = stateMap
        self.currentState = stateMap.startState
        main.schedule.add(sim.Event('think', main.schedule.time,
            (self.creature,), dests=[self.creature]))

    def listen(self, event):
        """
        Our host creature calls this for us on any events it receives.
        Handles:
            "think"    (creature)
        """
        if event.id == "think":
            creature = event.args[0]
            if creature is self.creature:

                # use our brain's statemap to determine what to do next
                transitions = self.stateMap.transitions
                for condition in transitions[self.currentState]:
                    conditionF = getattr(self, condition)
                    val = conditionF()
                    for transitionVal, availTransitions in \
                            transitions[self.currentState][condition].iteritems():
                        if val == transitionVal:
                            transition = chooseWeighted(availTransitions)
                            nextState, action = transition
                            self.currentState = nextState
                            actionF = getattr(self, action)
                            actionTime = actionF()
                            main.schedule.add(sim.Event('think', actionTime+1,
                                (self.creature,), dests=[self.creature]))
                            return

                # we didn't find anything to do. probably means a bug?
                parole.warn('***%r: no state transition conditions were met! '
                            'Thinking again in 1000ms.', self.creature)
                main.schedule.add(sim.Event('think', main.schedule.time+1000,
                    (self.creature,), dests=[self.creature]))


    def wait(self):
        parole.debug('%r: waiting.', self.creature)
        return main.schedule.time + self.waitTime
        #main.schedule.add(sim.Event('think', main.schedule.time + self.waitTime,
        #    (self.creature,), dests=[self.creature]))

class StateMap(object):
    def __init__(self, transitions, startState):
        self.transitions = transitions
        self.startState = startState


# general purpose statemap and brain that have the creature wait around until the
# player is "nearby", then pursues him, and flees if hp get too low.
defaultStateMap = StateMap(
    {
        'wait state': 
        {
            'nearPlayer':
            {
                True:  [(('approach state', 'approachPlayer'), 10)],
                False: [(('wait state', 'wait'), 10)]
            }
        },

        'approach state':
        {
            'nearPlayer':
            {
                True:  [(('approach state', 'approachPlayer'), 10)],
                False: [(('wait state', 'wait'), 10)]
            }
        }
    },

    'wait state' # start state
)

class DefaultBrain(Brain):

    fleePercentage = 0.2 # % of hp

    def nearPlayer(self):
        if not self.creature.parentTile or not main.player.parentTile:
            return False

        if self.creature.parentTile.map is not main.player.parentTile.map:
            return False

        # calculate how close the player has to be to be considered "near",
        # based on the creature's perception
        maxDistance = 10 + self.creature['perception'] - 10
        return distance(self.creature.pos, main.player.pos) <= maxDistance

    def approachPlayer(self):
        parole.debug('%r: approaching player', self.creature)

        if main.player.parentTile in adjacentTiles(self.creature.parentTile):
            if self.creature['hp'] > self.fleePercentage*self.creature['maxHP']:
                # We are already next to the player, so melee him
                return self.creature.melee(main.player)

        # move toward the player, if possible (no actual pathfinding yet, but
        # this is where it would go). otherwise, move randomly
        time = main.schedule.time + \
                self.creature.timeOfAction(self.creature.walkEnergy)
        diff = difference(main.player.pos, self.creature.pos)
        dx, dy = signVector(diff)

        # very simplistic flight mechanism for now -- if hp is below 15% of
        # maximum, reverse our direction, so as to flee rather than approach
        if self.creature['hp'] <= self.fleePercentage*self.creature['maxHP']:
            parole.debug('*** FLEEING ***')
            dx, dy = -dx, -dy

        dest = self.creature.pos[0] + dx, self.creature.pos[1] + dy
        neighbs = list(neighbors(self.creature.pos))
        random.shuffle(neighbs)

        map = self.creature.parentTile.map
        for d in [dest] + neighbs:
            if d[0] < 0 or d[1] < 0 or d[0] >= map.cols or d[1] >= map.rows:
                continue
            if not self.creature.parentTile.map[d].hasMoveBlocker():
                # destination is not blocked; schedule translocation thereto
                self.creature.doFatigue(self.creature.walkEnergy)
                main.schedule.add(sim.Event('translocate', main.schedule.time, 
                    args=(self.creature, d), dests=[self.creature]))
                return time

        parole.debug("%r: Egads, I'm trapped!", self.creature)
        return time


